//
// Created by ld016 on 17-1-24.
//

#include "Renderer.h"
#include "Vulkan_util.h"
#include "Context.h"
#include "Util.h"


class _Renderer_Impl{
public:
    _Renderer_Impl(GLFWwindow* window):context(window),utility(context){
        initialize();
    };

    void resize(int width, int height){
        if (width == 0 || height == 0) return;

        //std::tie(window_framebuffer_width, window_framebuffer_height) = context.getWindowFrameBufferSize();

        //recreateSwapChain();
    }

    void requestDraw(float deltatime){
        // TODO: there is graphics queue waiting in utility.copyBuffer() called by this so I don't need to sync CPU and GPU elsewhere... but someday I will make the copy command able to use multiple times and I need to sync on writing the staging buffer
        //updateUniformBuffers(deltatime);
        drawFrame();
    }
    void cleanUp(){
        context.getDevice().waitIdle();
    }

private:
    VContext context;
    VUtility utility;
    VRaii<vk::SwapchainKHR> swapchain;
    std::vector<vk::Image> swapchain_images;
    std::vector<VRaii<vk::ImageView>> swapchain_imageviews;
    std::vector<VRaii<vk::Framebuffer>> swapchain_framebuffers;
    VRaii<vk::RenderPass> render_pass;
    VRaii<vk::Sampler> texture_sampler;

    struct{
        VRaii<vk::RenderPass> pre_pass; // the depth prepass which happens before formal render pass
        // only one image buffer for depth because only one draw operation happens at one time
        VRaii<vk::Image> image;
        VRaii<vk::DeviceMemory> memory;
        VRaii<vk::ImageView> view;
        // for depth pre pass
        VRaii<vk::Image> pre_pass_image;
        VRaii<vk::DeviceMemory> pre_pass_memory;
        VRaii<vk::ImageView> pre_pass_view;

        VRaii<vk::Framebuffer> framebuffer;
    }depth;

    struct{
        VRaii<std::vector<vk::CommandBuffer>> render;
    }commands;

    VRaii<vk::DescriptorPool> descriptor_pool;

    struct{
        struct{
            VRaii<vk::DescriptorSetLayout> setlayout;
            VRaii<vk::PipelineLayout> layout;
            VRaii<vk::Pipeline> pipeline;
            VRaii<std::vector<vk::DescriptorSet>> set;
        }base;
    }pipeline;

    vk::Format swapchain_image_format;
    vk::Extent2D swapchain_extent;

    struct{
        struct{
            glm::mat4 mvp;
        }mvp;

        struct{
            glm::vec4 position;
            glm::vec4 color;
        }light;

        struct{
            VRaii<vk::Buffer> buffer;
            VRaii<vk::DeviceMemory> memory;
            VRaii<vk::Buffer> staging_buffer;
            VRaii<vk::DeviceMemory> staging_memory;
            vk::DescriptorBufferInfo descriptor;
        }mvp_buffer,light_buffer;

    }ubos;

    struct{
        VRaii<vk::Semaphore> image_available;
        VRaii<vk::Semaphore> render_finish;
    }semaphore;


    void createDepthResources();
    void createSwapChain();
    void createSwapChainImageViews();
    void createRenderPasses();
    void createTextureSampler();
    void createFrameBuffers();
    VRaii<vk::ShaderModule> createShaderModule(const std::vector<char>& code);
    void createUniformBuffer();
    void createLayout();
    void createPipeline();
    void createDescriptorSet();
    void createSemaphores();

    void updateUniformBuffer();
    void createGraphicsCommandBuffers();
    void drawFrame();

    void initialize()
    {
        createSwapChain();
        createSwapChainImageViews();
        createRenderPasses();
        createLayout();
        createPipeline();
        createDepthResources();
        createFrameBuffers();
        createUniformBuffer();
        updateUniformBuffer();
        createDescriptorSet();
        createGraphicsCommandBuffers();
        createSemaphores();
    }
};

void _Renderer_Impl::createSwapChain()
{
    auto support_details = SwapChainSupportDetails::querySwapChainSupport(context.getPhysicalDevice(), context.getWindowSurface());

    auto surface_format = utility.chooseSwapSurfaceFormat(support_details.formats);
    auto present_mode = utility.chooseSwapPresentMode(support_details.present_modes);
    auto extent = utility.chooseSwapExtent(support_details.capabilities);

    uint32_t queue_length = support_details.capabilities.minImageCount + 1;
    if (support_details.capabilities.maxImageCount > 0 && queue_length > support_details.capabilities.maxImageCount)
    {
        // 0 for maxImageCount means no limit
        queue_length = support_details.capabilities.maxImageCount;
    }

    auto create_info = vk::SwapchainCreateInfoKHR()
        .setSurface(context.getWindowSurface())
        .setMinImageCount(queue_length)
        .setImageFormat(surface_format.format)
        .setImageColorSpace(surface_format.colorSpace)
        .setImageExtent(extent)
        .setImageArrayLayers(1)
        .setImageUsage(vk::ImageUsageFlagBits::eColorAttachment)
        .setPreTransform(support_details.capabilities.currentTransform)
        .setPresentMode(present_mode)
        .setClipped(VK_TRUE);

    auto indices = context.getQueueFamilyIndices();
    uint32_t queueFamilyIndices[] = { (uint32_t)indices.graphics_family, (uint32_t)indices.present_family };

    if (indices.graphics_family != indices.present_family)
    {
        create_info.setImageSharingMode(vk::SharingMode::eConcurrent)
                .setQueueFamilyIndexCount(2)
                .setPQueueFamilyIndices(queueFamilyIndices);
    }
    else
    {
        create_info.setImageSharingMode(vk::SharingMode::eExclusive);
    }

    auto old_swap_chain = std::move(swapchain); //which will be destroyed when out of scope
    create_info.setOldSwapchain(old_swap_chain);

    auto tmp_swap_chain = context.getDevice().createSwapchainKHR(create_info);

    auto deleter = [this](vk::SwapchainKHR & obj){
        context.getDevice().destroySwapchainKHR(obj);
    };

    swapchain = {tmp_swap_chain,deleter};

    swapchain_images = context.getDevice().getSwapchainImagesKHR(tmp_swap_chain);

    swapchain_image_format = surface_format.format;
    swapchain_extent = extent;
}

void _Renderer_Impl::createSwapChainImageViews()
{
    swapchain_imageviews.clear(); // VRaii will delete old objects
    swapchain_imageviews.reserve(swapchain_images.size());

    for (uint32_t i = 0; i < swapchain_images.size(); i++)
    {
        auto tmp_imageview = utility.createImageView(swapchain_images[i], swapchain_image_format, vk::ImageAspectFlagBits::eColor);
        swapchain_imageviews.push_back(std::move(tmp_imageview));
    }
}

void _Renderer_Impl::createDepthResources()
{
    auto depth_format = utility.findDepthFormat();
    std::tie(depth.image, depth.memory) = utility.createImage(swapchain_extent.width, swapchain_extent.height
            , depth_format
            , vk::ImageTiling::eOptimal
            , vk::ImageUsageFlagBits::eDepthStencilAttachment
            , vk::MemoryPropertyFlagBits::eDeviceLocal);
    depth.view = utility.createImageView(depth.image, depth_format, vk::ImageAspectFlagBits::eDepth);
    utility.transitImageLayout(depth.image, vk::ImageLayout::eUndefined, vk::ImageLayout::eDepthStencilAttachmentOptimal);

    // for depth pre pass and output as texture
    std::tie(depth.pre_pass_image, depth.pre_pass_memory) = utility.createImage(swapchain_extent.width, swapchain_extent.height
            , depth_format
            , vk::ImageTiling::eOptimal
            , vk::ImageUsageFlags()|vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled
            , vk::MemoryPropertyFlagBits::eDeviceLocal);
    depth.pre_pass_view = utility.createImageView(depth.pre_pass_image, depth_format, vk::ImageAspectFlagBits::eDepth);
    utility.transitImageLayout(depth.pre_pass_image, vk::ImageLayout::eUndefined, vk::ImageLayout::eDepthStencilAttachmentOptimal);
}

void _Renderer_Impl::createRenderPasses()
{
    auto renderpass_deletef = [this](vk::RenderPass & obj)
    {
        context.getDevice().destroyRenderPass(obj);
    };

    // depth pre pass // TODO: should I merge this as a
    {
        auto depth_attachment = vk::AttachmentDescription()
            .setFormat(utility.findDepthFormat())
            .setLoadOp(vk::AttachmentLoadOp::eClear)
            .setStoreOp(vk::AttachmentStoreOp::eStore)
            .setStencilLoadOp(vk::AttachmentLoadOp::eDontCare)
            .setStencilStoreOp(vk::AttachmentStoreOp::eDontCare)
            .setInitialLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal)
            .setFinalLayout(vk::ImageLayout::eShaderReadOnlyOptimal);

        auto depth_attachment_ref = vk::AttachmentReference(0,vk::ImageLayout::eDepthStencilAttachmentOptimal);
        auto subpass = vk::SubpassDescription()
            .setPDepthStencilAttachment(&depth_attachment_ref);

        // overwrite subpass dependency to make it wait until VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
        auto dependency = vk::SubpassDependency()
            .setSrcSubpass(VK_SUBPASS_EXTERNAL)
            .setDstSubpass(0)
            .setSrcAccessMask(vk::AccessFlagBits::eMemoryRead)
            .setDstAccessMask(vk::AccessFlags()|vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite)
            .setSrcStageMask(vk::PipelineStageFlagBits::eTopOfPipe)
            .setDstStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput);

        std::array<vk::AttachmentDescription, 1> attachments = {  depth_attachment };

        auto render_pass_info = vk::RenderPassCreateInfo()
            .setAttachmentCount((uint32_t)attachments.size())
            .setPAttachments(attachments.data())
            .setSubpassCount(1)
            .setPSubpasses(&subpass)
            .setDependencyCount(1)
            .setPDependencies(&dependency);

        auto pass = context.getDevice().createRenderPass(render_pass_info);
        depth.pre_pass = {pass,renderpass_deletef};
    }
    // the render pass
    {
        auto color_attachment = vk::AttachmentDescription()
                .setFormat(swapchain_image_format)
                .setLoadOp(vk::AttachmentLoadOp::eClear)
                .setStoreOp(vk::AttachmentStoreOp::eStore)
                .setStencilLoadOp(vk::AttachmentLoadOp::eDontCare)
                .setStencilStoreOp(vk::AttachmentStoreOp::eDontCare)
                .setInitialLayout(vk::ImageLayout::eUndefined)
                .setFinalLayout(vk::ImageLayout::ePresentSrcKHR);
        auto depth_attachment = vk::AttachmentDescription()
                .setFormat(utility.findDepthFormat())
                .setLoadOp(vk::AttachmentLoadOp::eClear)
                .setStoreOp(vk::AttachmentStoreOp::eDontCare)
                .setStencilLoadOp(vk::AttachmentLoadOp::eDontCare)
                .setStencilStoreOp(vk::AttachmentStoreOp::eDontCare)
                .setInitialLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal)
                .setFinalLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal);
        auto color_attachment_ref = vk::AttachmentReference(0,vk::ImageLayout::eColorAttachmentOptimal);
        auto depth_attachment_ref = vk::AttachmentReference(1,vk::ImageLayout::eDepthStencilAttachmentOptimal);

        auto subpass = vk::SubpassDescription()
                .setColorAttachmentCount(1)
                .setPColorAttachments(&color_attachment_ref)
                .setPDepthStencilAttachment(&depth_attachment_ref);

        // overwrite subpass dependency to make it wait until VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
        auto dependency = vk::SubpassDependency()
                .setSrcSubpass(VK_SUBPASS_EXTERNAL)
                .setDstSubpass(0)
                .setSrcAccessMask(vk::AccessFlagBits::eMemoryRead)
                .setDstAccessMask(vk::AccessFlags()|vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite)
                .setSrcStageMask(vk::PipelineStageFlagBits::eTopOfPipe)
                .setDstStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput);

        std::array<vk::AttachmentDescription, 2> attachments = { color_attachment, depth_attachment };
        auto render_pass_info = vk::RenderPassCreateInfo()
                .setAttachmentCount((uint32_t)attachments.size())
                .setPAttachments(attachments.data())
                .setSubpassCount(1)
                .setPSubpasses(&subpass)
                .setDependencyCount(1)
                .setPDependencies(&dependency);

        auto pass = context.getDevice().createRenderPass(render_pass_info);
        render_pass = {pass,renderpass_deletef};
    }
}

void _Renderer_Impl::createTextureSampler()
{
    auto deleter = [this](vk::Sampler& obj){
        context.getDevice().destroySampler(obj);
    };

    auto create_info = vk::SamplerCreateInfo()
        .setMinFilter(vk::Filter::eLinear)
        .setMagFilter(vk::Filter::eLinear)
        .setAnisotropyEnable(VK_TRUE)
        .setMaxAnisotropy(16)
        .setBorderColor(vk::BorderColor::eIntOpaqueBlack)
        .setCompareEnable(VK_FALSE)
        .setCompareOp(vk::CompareOp::eAlways)
        .setMipmapMode(vk::SamplerMipmapMode::eLinear);

    auto sampler = context.getDevice().createSampler(create_info);
    texture_sampler = {sampler,deleter};
}

void _Renderer_Impl::createFrameBuffers()
{
    auto raii_framebuffer_deleter = [this](vk::Framebuffer& obj)
    {
        context.getDevice().destroyFramebuffer(obj);
    };

    // swap chain frame buffers
    {
        swapchain_framebuffers.clear(); // VDeleter will delete old objects
        swapchain_framebuffers.reserve(swapchain_imageviews.size());

        for (size_t i = 0; i < swapchain_imageviews.size(); i++)
        {
            std::array<vk::ImageView, 2> attachments = { swapchain_imageviews[i], depth.view };
            auto framebuffer_info = vk::FramebufferCreateInfo()
                .setRenderPass(render_pass)
                .setAttachmentCount((uint32_t)attachments.size())
                .setPAttachments(attachments.data())
                .setWidth(swapchain_extent.width)
                .setHeight(swapchain_extent.height)
                .setLayers(1);

            swapchain_framebuffers.emplace_back(
                    context.getDevice().createFramebuffer(framebuffer_info),
                    raii_framebuffer_deleter
            );
        }
    }

    // depth pass frame buffer
    {
        std::array<vk::ImageView, 1> attachments = { depth.pre_pass_view };
        auto framebuffer_info = vk::FramebufferCreateInfo()
                .setRenderPass(depth.pre_pass)
                .setAttachmentCount((uint32_t)attachments.size())
                .setPAttachments(attachments.data())
                .setWidth(swapchain_extent.width)
                .setHeight(swapchain_extent.height)
                .setLayers(1);

        depth.framebuffer = {context.getDevice().createFramebuffer(framebuffer_info), raii_framebuffer_deleter};
    }
}

void _Renderer_Impl::createUniformBuffer(){
    {
        vk::DeviceSize bufferSize = sizeof(ubos.mvp);
        std::tie(ubos.mvp_buffer.staging_buffer, ubos.mvp_buffer.staging_memory) = utility.createBuffer(bufferSize
                , vk::BufferUsageFlagBits::eTransferSrc
                , vk::MemoryPropertyFlags() | vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
        std::tie(ubos.mvp_buffer.buffer, ubos.mvp_buffer.memory) = utility.createBuffer(bufferSize
                , vk::BufferUsageFlags() | vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eUniformBuffer
                , vk::MemoryPropertyFlagBits::eDeviceLocal);
        ubos.mvp_buffer.descriptor = {ubos.mvp_buffer.buffer,0,bufferSize};
    }
    {
        vk::DeviceSize bufferSize = sizeof(ubos.light);
        std::tie(ubos.light_buffer.staging_buffer, ubos.light_buffer.staging_memory) = utility.createBuffer(bufferSize
                , vk::BufferUsageFlagBits::eTransferSrc
                , vk::MemoryPropertyFlags() | vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
        std::tie(ubos.light_buffer.buffer, ubos.light_buffer.memory) = utility.createBuffer(bufferSize
                , vk::BufferUsageFlags() | vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eUniformBuffer
                , vk::MemoryPropertyFlagBits::eDeviceLocal);
        ubos.light_buffer.descriptor = {ubos.light_buffer.buffer,0,bufferSize};
    }
}

void _Renderer_Impl::updateUniformBuffer(){
    auto cmd = utility.beginSingleTimeCommands();
    // mvp
    {
        vk::DeviceSize size = sizeof(ubos.mvp);
        auto pointer = context.getDevice().mapMemory(ubos.mvp_buffer.staging_memory,0,size);
        memcpy(pointer,&ubos.mvp,size);
        context.getDevice().unmapMemory(ubos.mvp_buffer.staging_memory);
        utility.recordCopyBuffer(cmd,ubos.mvp_buffer.staging_buffer,ubos.mvp_buffer.buffer,size);
    }
    //light
    {
        vk::DeviceSize size = sizeof(ubos.light);
        auto pointer = context.getDevice().mapMemory(ubos.light_buffer.staging_memory,0,size);
        memcpy(pointer,&ubos.light,size);
        context.getDevice().unmapMemory(ubos.light_buffer.staging_memory);
        utility.recordCopyBuffer(cmd,ubos.light_buffer.staging_buffer,ubos.light_buffer.buffer,size);
    }
    utility.endSingleTimeCommands(cmd);
}

VRaii<vk::ShaderModule> _Renderer_Impl::createShaderModule(const std::vector<char>& code)
{
    auto deleter = [this](vk::ShaderModule& obj){
        context.getDevice().destroyShaderModule(obj);
    };
    auto create_info = vk::ShaderModuleCreateInfo()
        .setCodeSize(code.size())
        .setPCode((uint32_t*)code.data());

    auto module = context.getDevice().createShaderModule(create_info);
    return VRaii<vk::ShaderModule>(module,deleter);
}

void _Renderer_Impl::createLayout(){
    vk::DescriptorPoolSize poolSize = {
            vk::DescriptorType::eUniformBuffer,2
    };
    auto set_pool_info = vk::DescriptorPoolCreateInfo(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,1,1,&poolSize);
    descriptor_pool = VRaii<vk::DescriptorPool>(context.getDevice().createDescriptorPool(set_pool_info),
    [this](vk::DescriptorPool& obj){context.getDevice().destroyDescriptorPool(obj);});

    //mvp and light uniform
    vk::DescriptorSetLayoutBinding binding[] = {
            {0,vk::DescriptorType::eUniformBuffer,1,vk::ShaderStageFlagBits::eVertex},
            {1,vk::DescriptorType::eUniformBuffer,1,vk::ShaderStageFlagBits::eFragment}
    };
    auto setlayout_info = vk::DescriptorSetLayoutCreateInfo()
        .setBindingCount(2).setPBindings(binding);
    auto setlayout = context.getDevice().createDescriptorSetLayout(setlayout_info);
    pipeline.base.setlayout = VRaii<vk::DescriptorSetLayout>(setlayout,[this](vk::DescriptorSetLayout& obj){
        context.getDevice().destroyDescriptorSetLayout(obj);
    });

    auto layout_info = vk::PipelineLayoutCreateInfo()
        .setSetLayoutCount(1).setPSetLayouts(&setlayout);
    auto layout = context.getDevice().createPipelineLayout(layout_info);
    pipeline.base.layout = VRaii<vk::PipelineLayout>(layout,[this](vk::PipelineLayout& obj){
        context.getDevice().destroyPipelineLayout(obj);
    });
}

void _Renderer_Impl::createPipeline(){
    auto deleter = [this](vk::Pipeline& obj)
    {
        context.getDevice().destroyPipeline(obj);
    };

    //base pipeline
    {
        auto vertShader = createShaderModule(util::readFile(util::getResourcePath("shader/base.vert.spv")));
        auto fragShader = createShaderModule(util::readFile(util::getResourcePath("shader/base.frag.spv")));
        std::vector<vk::PipelineShaderStageCreateInfo> shaderStages = {
                vk::PipelineShaderStageCreateInfo(vk::PipelineShaderStageCreateFlags(),vk::ShaderStageFlagBits::eVertex,vertShader,"main"),
                vk::PipelineShaderStageCreateInfo(vk::PipelineShaderStageCreateFlags(),vk::ShaderStageFlagBits::eFragment,fragShader,"main")
        };
        auto binding = vk::VertexInputBindingDescription(0,16);//3*float vertex + 4 normal compact
        vk::VertexInputAttributeDescription attribute[2] = {
                {0,0,vk::Format::eR32G32B32Sfloat,0},
                {1,0,vk::Format::eR8G8B8Snorm,12}
        };
        auto inputState = vk::PipelineVertexInputStateCreateInfo()
            .setVertexBindingDescriptionCount(1)
            .setPVertexBindingDescriptions(&binding)
            .setVertexAttributeDescriptionCount(2)
            .setPVertexAttributeDescriptions(attribute);
        auto inputAssemblyState = vk::PipelineInputAssemblyStateCreateInfo().setTopology(vk::PrimitiveTopology::eTriangleList);
        auto viewportState = vk::PipelineViewportStateCreateInfo().setViewportCount(1).setScissorCount(1);
        auto rasterizationState = vk::PipelineRasterizationStateCreateInfo().setDepthClampEnable(VK_TRUE).setLineWidth(1.0f);
        auto multisampleState = vk::PipelineMultisampleStateCreateInfo().setMinSampleShading(1.0f);
        auto depthStencilState = vk::PipelineDepthStencilStateCreateInfo()
            .setDepthTestEnable(VK_TRUE)
            .setDepthWriteEnable(VK_TRUE)
            .setMinDepthBounds(0.0f)
            .setMaxDepthBounds(1.0f)
            .setDepthCompareOp(vk::CompareOp::eLessOrEqual);
        auto colorBlendAttachment = vk::PipelineColorBlendAttachmentState()
            .setBlendEnable(VK_FALSE)
            .setColorWriteMask(vk::ColorComponentFlags()
                               | vk::ColorComponentFlagBits::eR
                               | vk::ColorComponentFlagBits::eG
                               | vk::ColorComponentFlagBits::eB
                               | vk::ColorComponentFlagBits::eA);
        auto colorBlendState = vk::PipelineColorBlendStateCreateInfo()
            .setAttachmentCount(1)
            .setPAttachments(&colorBlendAttachment);

        vk::DynamicState dynamic[2] = {
                vk::DynamicState::eViewport,vk::DynamicState::eScissor
        };
        auto dynamicState = vk::PipelineDynamicStateCreateInfo()
            .setDynamicStateCount(2).setPDynamicStates(dynamic);

        auto create_info = vk::GraphicsPipelineCreateInfo()
            .setStageCount(2).setPStages(shaderStages.data())
            .setPVertexInputState(&inputState)
            .setPInputAssemblyState(&inputAssemblyState)
            .setPViewportState(&viewportState)
            .setPRasterizationState(&rasterizationState)
            .setPMultisampleState(&multisampleState)
            .setPDepthStencilState(&depthStencilState)
            .setPColorBlendState(&colorBlendState)
            .setPDynamicState(&dynamicState)
            .setLayout(pipeline.base.layout)
            .setRenderPass(render_pass)
            .setSubpass(0);
        pipeline.base.pipeline = {context.getDevice().createGraphicsPipeline(VK_NULL_HANDLE,create_info),deleter};
    }
}

void _Renderer_Impl::createDescriptorSet(){
    auto deleter = [this](std::vector<vk::DescriptorSet>& obj){
        context.getDevice().freeDescriptorSets(descriptor_pool,obj);
    };
    //base unifom set
    {
        auto create_info = vk::DescriptorSetAllocateInfo(descriptor_pool,1,pipeline.base.setlayout.data());
        auto set = context.getDevice().allocateDescriptorSets(create_info);
        pipeline.base.set = {set,deleter};

        vk::WriteDescriptorSet writes[] = {
                {set[0],0,0,1,vk::DescriptorType::eUniformBuffer, nullptr,&ubos.mvp_buffer.descriptor},
                {set[0],1,0,1,vk::DescriptorType::eUniformBuffer, nullptr,&ubos.light_buffer.descriptor}
        };
        context.getDevice().updateDescriptorSets(2,writes,0,nullptr);
    }

}

void _Renderer_Impl::createSemaphores(){
    auto deleter = [this](vk::Semaphore& obj){
        context.getDevice().destroySemaphore(obj);
    };
    auto create_info = vk::SemaphoreCreateInfo();
    {
        auto obj = context.getDevice().createSemaphore(create_info);
        semaphore.image_available = {obj,deleter};
    }
    {
        auto obj = context.getDevice().createSemaphore(create_info);
        semaphore.render_finish = {obj,deleter};
    }
}

void _Renderer_Impl::drawFrame()
{
    // 1. Acquiring an image from the swap chain
    uint32_t image_index;
    {
        auto acquire = context.getDevice().acquireNextImageKHR(swapchain,UINT64_MAX,semaphore.image_available,VK_NULL_HANDLE);
        image_index = acquire.value;
        if (acquire.result == vk::Result::eErrorOutOfDateKHR)
        {
            // when swap chain needs recreation
            //recreateSwapChain();
            return;
        }
    }

    // 2. Submitting the command buffer
    {
        vk::PipelineStageFlags wait_stages[] = { vk::PipelineStageFlagBits::eColorAttachmentOutput };
        auto cmd = commands.render.get()[image_index];
        auto submit_info = vk::SubmitInfo(1,semaphore.image_available.data(),wait_stages,1,&cmd,1,semaphore.render_finish.data());
        context.getGraphicsQueue().submit(submit_info,VK_NULL_HANDLE);
    }
    // TODO: use Fence and we can have cpu start working at a earlier time

    // 3. Submitting the result back to the swap chain to show it on screen
    {
        auto present_info = vk::PresentInfoKHR(1,semaphore.render_finish.data(),1,swapchain.data(),&image_index);
        auto result = context.getPresentQueue().presentKHR(present_info);
        if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR)
        {
            //recreateSwapChain();
        }
    }
}

void _Renderer_Impl::createGraphicsCommandBuffers()
{
    commands.render = utility.createCommandBuffers(swapchain_framebuffers.size());
    auto& cmds = commands.render.get();
    // record command buffers
    for (size_t i = 0; i < cmds.size(); i++)
    {
        auto begin_info = vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eSimultaneousUse);
        cmds[i].begin(begin_info);
        // render pass
        {
            vk::Rect2D render_area = {{0,0},swapchain_extent};
            auto clearColor = vk::ClearColorValue().setFloat32({0.0f, 0.0f, 0.0f, 1.0f});
            auto clearDepthStencil = vk::ClearDepthStencilValue(1.0f,0);
            vk::ClearValue clear[2] = {clearColor,clearDepthStencil};

            auto pass_info = vk::RenderPassBeginInfo(render_pass,swapchain_framebuffers[i],render_area,2,clear);
            cmds[i].beginRenderPass(pass_info,vk::SubpassContents::eInline);

            cmds[i].bindPipeline(vk::PipelineBindPoint::eGraphics,pipeline.base.pipeline);
            cmds[i].bindDescriptorSets(vk::PipelineBindPoint::eGraphics,pipeline.base.layout,0,pipeline.base.set.get(), nullptr);

            //draw
            cmds[i].endRenderPass();
        }

        cmds[i].end();
    }
}

Renderer::Renderer(GLFWwindow *window):p_impl(std::make_unique<_Renderer_Impl>(window)) {

}

Renderer::~Renderer() = default;

void Renderer::resize(int width, int height)
{
    p_impl->resize(width, height);
}

void Renderer::requestDraw(float deltatime)
{
    p_impl->requestDraw(deltatime);
}

void Renderer::cleanUp()
{
    p_impl->cleanUp();
}
